/*
 * Copyright © 2009 Perseus Project - Tufts University <http://www.perseus.tufts.edu>
 *
 * This file is part of UniCollatorPerseus.
 *
 * AlignmentPerseus is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * AlignmentPerseus is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * You should have received a copy of the GNU General Public License
 * along with AlignmentPerseus.  If not, see <http://www.gnu.org/licenses/>.
 */

package eu.himeros.alignment;

import eu.himeros.util.DoubleComparator;
import eu.himeros.util.IoManager;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.util.Arrays;
import java.util.Comparator;
import java.util.Hashtable;
import java.util.Map;
import java.util.HashSet;
import java.util.Vector;

/**
 * Create the Error Pattern Files.
 *
 * @author Federico Boschetti <federico.boschetti.73@gmail.com>
 */
public class ErrorPatternMaker {
    private String errPat1="";
    private String errPat2="";
    private String key="";
    private Double one=new Double(1.0);
    private Double cnt=new Double(0.0);
    private Hashtable<String,Double> ht=new Hashtable<String,Double>();
    private Hashtable<String,Double> htCm=new Hashtable<String,Double>(); // column marginals
    private Hashtable<String,Double> htRm=new Hashtable<String,Double>(); //row marginals
    private HashSet<String> errorSet=new HashSet<String>();
    private HashSet<String> errNgramSet=new HashSet<String>();
    private Object[] arr;
    private double totSum=0.0; // init sum of column (or row) marginals
    private int engineNum=3; // number of engines: in this implementation it is fixed to 3;
    private char gapChar='\u00B0'; //default gap character
    private int nglen=4; // default ngram length

    /**
     * Default Constructor.
     */
    public ErrorPatternMaker(){}

    /**
     * Add an error pattern.
     * @param str1 the OCR chunk.
     * @param str2 the ground truth chunk.
     */
    public void add(String str1, String str2){
        int ng=0;
        int eq=0;
        int len=str1.length();
        for(int i=0;i<len;i++){
            errPat1="";
            errPat2="";
            cnt=new Double(0.0);
            ng=0;
            while(ng<nglen){
                if(i+ng>=str1.length()){
                    str1=str1+gapChar;
                    str2=str2+gapChar;
                }
                errPat1+=""+str1.charAt(i+ng);
                errPat2+=""+str2.charAt(i+ng);
                eq=checkNgram(errPat1,errPat2);
                key=(ng+1)+"\t"+errPat2+"\t"+errPat1;
                cnt=ht.get(key);
                if(cnt==null){
                    ht.put(key,one);
                }else{
                    ht.put(key,new Double(cnt.doubleValue()+1.0));
                }
                if(eq==0 && (ng+1)>1) errNgramSet.add((ng+1)+"\t"+errPat2);
                ng++;
            }
        }
    }

    /**
     * Assign the probability that an OCR character output is correct.
     */
    //TODO ATTENTION!!! HERE THERE IS A BIG PROBLEM WITH ROW AND COLUMN INVERSION: VERIFY!!!
    public void assignProbability(){
        String[] ngGtOcr=null;
        Double rowVal=null;
        Double colVal=null;
        double dval=0.0;
        double rm=0.0; // row marginal
        double cm=0.0; // col marginal
        double charProb=0.0; //char probability
        //make marginals of columns and rows
        for(String keyStr:ht.keySet()){
            ngGtOcr=keyStr.split("\t");
            dval=ht.get(keyStr).doubleValue();
            computeMarginals(ngGtOcr,dval);
        }
        //assign probabilities to cells
        for(String keyStr:ht.keySet()){
            ngGtOcr=keyStr.split("\t");
            dval=ht.get(keyStr).doubleValue();
            colVal=htCm.get(ngGtOcr[1]);
            cm=colVal.doubleValue();
            charProb=Math.pow(cm/totSum,1/engineNum); // NEW
            rowVal=htRm.get(ngGtOcr[2]);
            rm=rowVal.doubleValue();
            //ht.put(keyStr, new Double((dval/cm)*charProb)); // NEW <-- ATTENTION!!! cm instead of rm!!!
            ht.put(keyStr, new Double((dval/rm)*charProb));
            //ht.put(keyStr, new Double(dval/rm)); // OLD
        }
    }

    /**
     * Compute Column and Row marginals
     * @param ngGtOcr
     * @param dval
     */
    private void computeMarginals(String[] ngGtOcr,double dval){
                cnt=htCm.get(ngGtOcr[1]);
                if(cnt==null){cnt=new Double(0.0);}
                htCm.put(ngGtOcr[1],new Double(cnt.doubleValue()+dval));
                cnt=htRm.get(ngGtOcr[2]);
                if(cnt==null){cnt=new Double(0.0);}
                htRm.put(ngGtOcr[2],new Double(cnt.doubleValue()+dval));
                totSum+=dval;
    }

    /**
     * Set the number of OCR engines.
     * @param engineNum the number of OCR engines.
     */
    public void setEngineNum(int engineNum){
        this.engineNum=engineNum;
    }

    /**
     * Check if the ngrams are equal (1) or not (0).
     * @param str1 the first ngram.
     * @param str2 the second ngram.
     * @return the comparison result (1 if equal, 0 if different).
     */
    private int checkNgram(String str1, String str2){
        for(int i=0;i<str1.length();i++){
            if(str1.charAt(i)==str2.charAt(i)) return 1;
        }
        return 0;
    }

    /**
     * Make the error set.
     */
    public void makeErrorSet(){
        for(String keyStr:ht.keySet()){
            errorSet.add(keyStr.split("\t")[0]);
        }
    }

    /**
     * Get the array of error patterns.
     * @return the array of error patterns.
     */
    public Object[] getErrorPatterns(){
        arr=ht.entrySet().toArray();
        DoubleComparator ic=new DoubleComparator();
        ic.switchReverse();
        Arrays.sort(arr,(Comparator)ic);
        return arr;
    }

    /**
     * Write the error patterns to the file
     *
     * @param bw the BufferedWriter that writes into the file.
     */
    public void writeErrorPatterns(BufferedWriter bw){
        String keyMap=null;
        String[] keyItems=null;
        double valMap;
        assignProbability();
        getErrorPatterns();
        for(int i=0;i<arr.length;i++){
            try{
                keyMap=(String)((Map.Entry)arr[i]).getKey();
                keyItems=keyMap.split("\t");
                valMap=((Double)((Map.Entry)(arr[i])).getValue()).doubleValue();
                if(valMap==1.0&&keyItems[1].equals(keyItems[2])){

                }else{
                    bw.write(keyMap+"\t"+valMap);bw.newLine();
                }
            }catch(Exception ex){ex.printStackTrace();}
        }
    }

    /**
     * Set the gap character.
     *
     * @param gapChar the gap character.
     */
    public void setGapChar(char gapChar){
        this.gapChar=gapChar;
    }

    /**
     * Set the gap character, providing a single character string.
     * @param gapTag the single character string containing the gap tag.
     */
    public void setGapTag(String gapTag){
        this.gapChar=gapTag.charAt(0);
    }

    public void make(String[] ocrFileNames, String[] gtFileNames, String outFileName){
        StringAligner alg=null;
        String algs[]=null;
        BufferedReader br=null;
        BufferedWriter bw=IoManager.getWriter(outFileName);
        String line=null;
        int len=2;
        for(int k=0;k<ocrFileNames.length;k++){
            String[] inFileNames=new String[len];
            inFileNames[0]=ocrFileNames[k];
            inFileNames[1]=gtFileNames[k];
            String[] texts=new String[len];
            alg=new StringAligner(new UpperCaseSimEvaluator());
            for(int i=0;i<len;i++){
                br=IoManager.getReader(inFileNames[i]);
                try{
                    while((line=br.readLine())!=null){
                        texts[i]+=line+"\u00B6"; //substitute CR with the paragraph sign
                    }
                }catch(Exception ex){ex.printStackTrace();}
            }
            char c;
            int[] beg=new int[len];
            int[] end=new int[len];
            for(int i=0;i<len;i++){
                beg[i]=0;
            }
            AnchorFinder af=new AnchorFinder(inFileNames,"[^\\u0370-\\u03FF\\u1F00-\\u1FFF\\u2019]");
            int[][] anchors=af.getAnchorPositions();
            for(int j=0;j<anchors[0].length;j++){
                for(int i=0;i<len;i++){
                    end[i]=anchors[i][j];
                    while(end[i]<texts[i].length()&&(c=texts[i].charAt(end[i]))!=' '&&c!='\u00B6'){
                        end[i]++;
                    }
                    end[i]++;
                }
                try{
                    if (end[0]>beg[0] && end[1]>beg[1]){
                        algs=alg.align(texts[0].substring(beg[0],end[0]),texts[1].substring(beg[1],end[1]));
                        add(algs[0],algs[1]);
                    }
                }catch(Exception ex){ex.printStackTrace();continue;}
                for(int i=0;i<len;i++) beg[i]=end[i];
            }
        }
        makeErrorSet();
        writeErrorPatterns(bw);
        try{
            bw.close();
        }catch(Exception ex){ex.printStackTrace();}
    }

    public void make(File[] ocrFiles, File[] gtFiles, String outFileName){
        String[] ocrFileNames=new String[ocrFiles.length];
        String[] gtFileNames=new String[gtFiles.length];
        for(int i=0;i<ocrFiles.length;i++){
            ocrFileNames[i]=ocrFiles[i].getAbsolutePath();
            gtFileNames[i]=gtFiles[i].getAbsolutePath();
        }
        make(ocrFileNames,gtFileNames,outFileName);
    }

}